//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2011 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_ALGORITHM_BINARY_H_
#define _BUOLA_ALGORITHM_BINARY_H_

#include <buola/iterator/range.h>

namespace buola {

///\addtogroup algorithm
///@{

///\name Binary
//@{

namespace detail {
    
    template<typename tType,typename tIterator>
    inline void binary_convert(tType &pDst,tIterator pIt,size_t pLen,
                               std::true_type qInteger,std::false_type qFloat,std::true_type qSigned,
                               std::integral_constant<EEndianness,EEndianness::LITTLE> qEndianness)
    {
        uint8_t *lDst=reinterpret_cast<uint8_t*>(&pDst);

        int lLen=std::min(pLen,sizeof(pDst));
        for(int i=0;i<lLen;i++)
            lDst[i]=pIt[i];
        ///\todo fix it for negative numbers
        for(int i=lLen;i<sizeof(pDst);i++)
            lDst[i]=0;
    }
    
    template<typename tType,typename tIterator>
    inline void binary_convert(tType &pDst,tIterator pIt,size_t pLen,
                               std::true_type qInteger,std::false_type qFloat,std::true_type qSigned,
                               std::integral_constant<EEndianness,EEndianness::BIG> qEndianness)
    {
        uint8_t *lDst=reinterpret_cast<uint8_t*>(&pDst);

        int lLen=std::min(pLen,sizeof(pDst));
        for(int i=0;i<lLen;i++)
            lDst[i]=pIt[pLen-i-1];
        ///\todo fix it for negative numbers
        for(int i=lLen;i<sizeof(pDst);i++)
            lDst[i]=0;
    }
    
    template<typename tType,typename tIterator>
    inline void binary_convert(tType &pDst,tIterator pIt,size_t pLen,
                               std::true_type qInteger,std::false_type qFloat,std::false_type qSigned,
                               std::integral_constant<EEndianness,EEndianness::LITTLE> qEndianness)
    {
        uint8_t *lDst=reinterpret_cast<uint8_t*>(&pDst);

        int lLen=std::min(pLen,sizeof(pDst));
        for(int i=0;i<lLen;i++)
            lDst[i]=pIt[i];
        for(int i=lLen;i<sizeof(pDst);i++)
            lDst[i]=0;
    }
    
    template<typename tType,typename tIterator>
    inline void binary_convert(tType &pDst,tIterator pIt,size_t pLen,
                               std::true_type qInteger,std::false_type qFloat,std::false_type qSigned,
                               std::integral_constant<EEndianness,EEndianness::BIG> qEndianness)
    {
        uint8_t *lDst=reinterpret_cast<uint8_t*>(&pDst);

        int lLen=std::min(pLen,sizeof(pDst));
        for(int i=0;i<lLen;i++)
            lDst[i]=pIt[pLen-i-1];
        for(int i=lLen;i<sizeof(pDst);i++)
            lDst[i]=0;
    }
    
    template<typename tType,typename tIterator>
    inline void binary_convert(tType &pDst,tIterator pIt,size_t pLen,
                               std::false_type qInteger,std::true_type qFloat,std::true_type qSigned,
                               std::integral_constant<EEndianness,EEndianness::LITTLE> qEndianness)
    {
        uint8_t *lDst=reinterpret_cast<uint8_t*>(&pDst);

        if(pLen!=sizeof(pDst))
            throw XData("invalid size in conversion of floating point type");
        for(int i=0;i<sizeof(pDst);i++)
            lDst[i]=pIt[i];
    }
    
    template<typename tType,typename tIterator>
    inline void binary_convert(tType &pDst,tIterator pIt,size_t pLen,
                               std::false_type qInteger,std::true_type qFloat,std::true_type qSigned,
                               std::integral_constant<EEndianness,EEndianness::BIG> qEndianness)
    {
        uint8_t *lDst=reinterpret_cast<uint8_t*>(&pDst);

        if(pLen!=sizeof(pDst))
            throw XData("invalid size in conversion of floating point type");
        for(int i=0;i<sizeof(pDst);i++)
            lDst[i]=pIt[sizeof(pDst)-i-1];
    }
    
/*namespace detail*/ }
        
template<typename tType,EEndianness vEndianness=EEndianness::SYSTEM,typename tIterator>
tType binary_interpret(tIterator pBegin,tIterator pEnd)
{
    static_assert(sizeof(typename std::iterator_traits<tIterator>::value_type)==1,"wrong value type in binary_interpret");
    if(pEnd-pBegin!=sizeof(tType))
        throw XData("wrong length in binary_interpret");
    
    tType lValue;
    detail::binary_convert(lValue,pBegin,sizeof(tType),std::is_integral<tType>(),std::is_floating_point<tType>(),
                           std::is_signed<tType>(),std::integral_constant<EEndianness,vEndianness>());
    return lValue;
}

template<typename tType,EEndianness vEndianness=EEndianness::SYSTEM,typename tRange>
inline tType binary_interpret(tRange &&pRange)
{
    return binary_interpret<tType,vEndianness>(range_begin(pRange),range_end(pRange));
}

template<typename tIterator>
std::string binary_to_string(tIterator pBegin,tIterator pEnd)
{
    static_assert(sizeof(typename std::iterator_traits<tIterator>::value_type)==1,"wrong value type in binary_interpret");
    return std::string(pBegin,pEnd);
}

template<typename tRange>
std::string binary_to_string(tRange &&pRange)
{
    return binary_to_string(range_begin(pRange),range_end(pRange));
}

//@}

///@}

/*namespace buola*/ }

#endif
